﻿using Common;
using OpenTK;
using OpenTK.Graphics.OpenGL;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace ImageProcessPlugins
{
    public partial class ImageWalkForm : Form
    {
        const int ControlPointSize = 6;
        public ImageWalkForm(Document doc)
        {
            InitializeComponent();
            
            this.doc = doc;
            cpView.SetDocument(doc);
            cpView.ViewMode = ViewMode.BestFit;
            InitControlPoints();
        }

        Rectangle backRect;
        Point vanishPoint;

        private void InitControlPoints()
        {
            backRect = new Rectangle(doc.GetBitmap().Width/5, doc.GetBitmap().Height/5, doc.GetBitmap().Width*3/5,
                                    doc.GetBitmap().Height*3/5);
            vanishPoint = new Point(doc.GetBitmap().Width / 2, doc.GetBitmap().Height / 2);
        }

        void glControl_Paint(object sender, PaintEventArgs e)
        {
            glControl.MakeCurrent();
            GL.ClearColor(SystemColors.ControlDark);
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
            DrawScene();
            glControl.SwapBuffers();
        }

        private void DrawScene()
        {
            if (scene != null)
            {
                cam.Transform();
                GL.Enable(EnableCap.Texture2D);
                scene.Draw();
            }
        }

        void glControl_Resize(object sender, EventArgs e)
        {
            if (glControl.ClientSize.Height == 0)
                glControl.ClientSize = new System.Drawing.Size(glControl.ClientSize.Width, 1);
            var bitmap = doc.GetBitmap();
            ResetViewPort(bitmap.Width, bitmap.Height);
        }

        private void ResetViewPort(int imwidth, int imheight)
        {
            float scale = Math.Min(glControl.Height / (float)imheight, glControl.Width / (float)imwidth);
            float w = imwidth * scale;
            float h = imheight * scale;
            GL.Viewport((int)(glControl.Width - w) / 2, (int)(glControl.Height - h) / 2, (int)w, (int)h);
        }

        int curStep = 0;
        Document doc;

        public Bitmap Bitmap { get { return doc.GetBitmap(); } }

        private void ImageWalkForm_Load(object sender, EventArgs e)
        {
            pnlStep1.Dock = DockStyle.Fill;
            pnlStep2.Dock = DockStyle.Fill;
            pnlStep2.Visible = false;
        }

        private void btnNext_Click(object sender, EventArgs e)
        {
            if (curStep == 0)
            {
                curStep = 1;
                UpdateStep();
            }
            else
            {
                Finish();
            }
        }

        private void Finish()
        {
            int FBOHandle, ColorTexture, DepthTexture;
            int Width = doc.GetBitmap().Width;
            int Height = doc.GetBitmap().Height;

            // Create Color Tex
            GL.GenTextures(1, out ColorTexture);
            GL.BindTexture(TextureTarget.Texture2D, ColorTexture);
            GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba8, Width, Height, 0, OpenTK.Graphics.OpenGL.PixelFormat.Rgba, PixelType.UnsignedByte, IntPtr.Zero);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToBorder);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToBorder);
            // GL.Ext.GenerateMipmap( GenerateMipmapTarget.Texture2D );

            // Create Depth Tex
            GL.GenTextures(1, out DepthTexture);
            GL.BindTexture(TextureTarget.Texture2D, DepthTexture);
            GL.TexImage2D(TextureTarget.Texture2D, 0, (PixelInternalFormat)All.DepthComponent32, Width, Height, 0, OpenTK.Graphics.OpenGL.PixelFormat.DepthComponent, PixelType.UnsignedInt, IntPtr.Zero);
            // things go horribly wrong if DepthComponent's Bitcount does not match the main Framebuffer's Depth
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToBorder);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToBorder);
            // GL.Ext.GenerateMipmap( GenerateMipmapTarget.Texture2D );

            // Create a FBO and attach the textures
            GL.Ext.GenFramebuffers(1, out FBOHandle);
            GL.Ext.BindFramebuffer(FramebufferTarget.FramebufferExt, FBOHandle);
            GL.Ext.FramebufferTexture2D(FramebufferTarget.FramebufferExt, FramebufferAttachment.ColorAttachment0Ext, TextureTarget.Texture2D, ColorTexture, 0);
            GL.Ext.FramebufferTexture2D(FramebufferTarget.FramebufferExt, FramebufferAttachment.DepthAttachmentExt, TextureTarget.Texture2D, DepthTexture, 0);

            GL.PushAttrib(AttribMask.ViewportBit);
            {
                GL.Viewport(0, 0, Width, Height);

                GL.ClearColor(0f, 0f, 0f, 0f);
                GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

                DrawScene();
            }
            GL.PopAttrib();
            GL.Ext.BindFramebuffer(FramebufferTarget.FramebufferExt, 0); // disable rendering into the FBO
            var bmp = new Bitmap(Width, Height);
            var data = bmp.LockBits(new Rectangle(0, 0, Width, Height), ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppRgb);
            GL.BindTexture(TextureTarget.Texture2D, ColorTexture);
            GL.GetTexImage(TextureTarget.Texture2D, 0, OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, data.Scan0);
            bmp.UnlockBits(data);
            var flipped = TransformPlugins.VerticalFlip(bmp);
            bmp.Dispose();
            doc.SetBitmap(flipped);
            GL.DeleteFramebuffers(1, ref FBOHandle);
            GL.DeleteTexture(ColorTexture);
            GL.DeleteTexture(DepthTexture);
            DialogResult = System.Windows.Forms.DialogResult.OK;
        }

        private void UpdateStep()
        {
            if (curStep == 0)
            {
                pnlStep1.Visible = true;
                pnlStep2.Visible = false;
                btnPrevious.Enabled = false;
                btnNext.Text = "&Next";
            }
            else
            {
                if (scene != null)
                    scene.Dispose();
                scene = null;
                
                pnlStep1.Visible = false;
                pnlStep2.Visible = true;
                btnPrevious.Enabled = true;
                btnNext.Text = "&Finish";
                scene = new SceneContext(doc.GetBitmap(), vanishPoint, backRect);
                scene.WriteObj("test.obj");
            }
        }

        private void btnPrevious_Click(object sender, EventArgs e)
        {
            if (curStep == 1)
            {
                curStep = 0;
                UpdateStep();
            }
        }

        private void cpView_DrawOverlay(Graphics g, Rectangle imageRect)
        {
            Rectangle screenRect = GetScreenSpaceRectangle();

            g.DrawRectangle(Pens.Red, screenRect);
            // Draw Control Points
            Rectangle rect = GetScreenSpaceRectangle();

            Rectangle r0 = new Rectangle(rect.Left - ControlPointSize, rect.Top - ControlPointSize,
                ControlPointSize * 2, ControlPointSize * 2);
            g.FillRectangle(Brushes.Red, r0);

            r0 = new Rectangle(rect.Left - ControlPointSize, rect.Top + rect.Size.Height / 2 - ControlPointSize, ControlPointSize * 2,
                ControlPointSize * 2);
            g.FillRectangle(Brushes.Red, r0);

            r0 = new Rectangle(rect.Left - ControlPointSize, rect.Top + rect.Size.Height - ControlPointSize, ControlPointSize * 2,
                ControlPointSize * 2);
            g.FillRectangle(Brushes.Red, r0);

            r0 = new Rectangle(rect.Left + rect.Size.Width / 2 - ControlPointSize, rect.Top + rect.Size.Height - ControlPointSize, ControlPointSize * 2,
               ControlPointSize * 2);
            g.FillRectangle(Brushes.Red, r0);

            r0 = new Rectangle(rect.Left + rect.Size.Width - ControlPointSize, rect.Top + rect.Size.Height - ControlPointSize, ControlPointSize * 2,
               ControlPointSize * 2);
            g.FillRectangle(Brushes.Red, r0);

            r0 = new Rectangle(rect.Left + rect.Size.Width - ControlPointSize, rect.Top + rect.Size.Height / 2 - ControlPointSize, ControlPointSize * 2,
               ControlPointSize * 2);
            g.FillRectangle(Brushes.Red, r0);

            r0 = new Rectangle(rect.Left + rect.Size.Width - ControlPointSize, rect.Top - ControlPointSize, ControlPointSize * 2,
              ControlPointSize * 2);
            g.FillRectangle(Brushes.Red, r0);

            r0 = new Rectangle(rect.Left + rect.Size.Width / 2 - ControlPointSize, rect.Top - ControlPointSize, ControlPointSize * 2,
              ControlPointSize * 2);
            g.FillRectangle(Brushes.Red, r0);

            Point ssVanishPoint = cpView.ImageSpacePointToScreenSpacePoint(vanishPoint);
            r0 = new Rectangle(ssVanishPoint.X - ControlPointSize, ssVanishPoint.Y - ControlPointSize, ControlPointSize * 2,
              ControlPointSize * 2);
            g.FillRectangle(Brushes.Red, r0);

            Point p0 = new Point(rect.Left, rect.Top);
            DrawAuxLine(g, ssVanishPoint, p0);
            p0 = new Point(rect.Left, rect.Bottom);
            DrawAuxLine(g, ssVanishPoint, p0);
            p0 = new Point(rect.Right, rect.Bottom);
            DrawAuxLine(g, ssVanishPoint, p0);
            p0 = new Point(rect.Right, rect.Top);
            DrawAuxLine(g, ssVanishPoint, p0);

            
        }

        private void DrawAuxLine(Graphics g, Point p0, Point p1)
        {
            var imgRect = GetScreenSpaceRectangle(new Rectangle(0, 0, doc.GetBitmap().Width, doc.GetBitmap().Height));
            int dx = p1.X - p0.X;
            int dy = p1.Y - p0.Y;
            float t = float.MaxValue;
            if (dx < 0)
                t = Math.Min(t, (imgRect.X- p0.X) / (float)dx);
            if (dx > 0)
                t = Math.Min(t, (imgRect.Right - p0.X) / (float)dx);
            if (dy < 0)
                t = Math.Min(t, (imgRect.Y- p0.Y) / (float)dy);
            if (dy > 0)
                t = Math.Min(t, (imgRect.Bottom - p0.Y) / (float)dy);
            using (var pen = new Pen(Brushes.Red))
            {
                pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot;
                pen.Color = Color.FromArgb(127, 255, 0, 0);
                g.DrawLine(pen, p0, p1);
                pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Solid;
                pen.Width = 2;
                pen.Color = Color.FromArgb(220, 255, 0, 0);
                g.DrawLine(pen, p1.X, p1.Y, (float)(p0.X + dx * t), (float)(p0.Y + dy * t));
            }
        }

        private void view_ImageMouseDown(int x, int y)
        {
            switch (state)
            {
                case OperationState.CreatedRectangle:
                    {
                        ox = x;
                        oy = y;
                        state = GetNextState(x, y);
                    }
                    break;
            }
        }

        OperationState GetNextState(int x, int y)
        {
            Rectangle rect = GetScreenSpaceRectangle();
            Point p = cpView.ImageSpacePointToScreenSpacePoint(new Point(x, y));
            Point ssVanishPoint = cpView.ImageSpacePointToScreenSpacePoint(vanishPoint);
            Rectangle r0 = new Rectangle(rect.Left - ControlPointSize, rect.Top - ControlPointSize,
                ControlPointSize * 2, ControlPointSize * 2);
            if (r0.Contains(p))
                return OperationState.ResizeTopLeft;
            r0 = new Rectangle(rect.Left - ControlPointSize, rect.Top + rect.Size.Height / 2 - ControlPointSize, ControlPointSize * 2,
                ControlPointSize * 2);
            if (r0.Contains(p))
                return OperationState.ResizeLeft;

            r0 = new Rectangle(rect.Left - ControlPointSize, rect.Top + rect.Size.Height - ControlPointSize, ControlPointSize * 2,
                ControlPointSize * 2);
            if (r0.Contains(p))
                return OperationState.ResizeLeftBottom;

            r0 = new Rectangle(rect.Left + rect.Size.Width / 2 - ControlPointSize, rect.Top + rect.Size.Height - ControlPointSize, ControlPointSize * 2,
               ControlPointSize * 2);
            if (r0.Contains(p))
                return OperationState.ResizeBottom;

            r0 = new Rectangle(rect.Left + rect.Size.Width - ControlPointSize, rect.Top + rect.Size.Height - ControlPointSize, ControlPointSize * 2,
               ControlPointSize * 2);
            if (r0.Contains(p))
                return OperationState.ResizeRightBottom;

            r0 = new Rectangle(rect.Left + rect.Size.Width - ControlPointSize, rect.Top + rect.Size.Height / 2 - ControlPointSize, ControlPointSize * 2,
               ControlPointSize * 2);
            if (r0.Contains(p))
                return OperationState.ResizeRight;

            r0 = new Rectangle(rect.Left + rect.Size.Width - ControlPointSize, rect.Top - ControlPointSize, ControlPointSize * 2,
              ControlPointSize * 2);
            if (r0.Contains(p))
                return OperationState.ResizeTopRight;

            r0 = new Rectangle(rect.Left + rect.Size.Width / 2 - ControlPointSize, rect.Top - ControlPointSize, ControlPointSize * 2,
              ControlPointSize * 2);
            if (r0.Contains(p))
                return OperationState.ResizeTop;

            r0 = new Rectangle(ssVanishPoint.X - ControlPointSize, ssVanishPoint.Y - ControlPointSize, ControlPointSize * 2,
              ControlPointSize * 2);
            if (r0.Contains(p))
                return OperationState.MoveVanishPoint;

            return OperationState.CreatedRectangle;

        }

        int ox, oy;
        int nx, ny;
        OperationState state = OperationState.CreatedRectangle;

        private void view_ImageMouseMove(int x, int y)
        {
            
            switch (state)
            {
                case OperationState.CreatedRectangle:
                    {
                        switch (GetNextState(x, y))
                        {
                            case OperationState.ResizeBottom:
                            case OperationState.ResizeTop:
                                cpView.Cursor = Cursors.SizeNS;
                                break;
                            case OperationState.ResizeLeft:
                            case OperationState.ResizeRight:
                                cpView.Cursor = Cursors.SizeWE;
                                break;
                            case OperationState.ResizeTopLeft:
                            case OperationState.ResizeRightBottom:
                                cpView.Cursor = Cursors.SizeNWSE;
                                break;
                            case OperationState.ResizeTopRight:
                            case OperationState.ResizeLeftBottom:
                                cpView.Cursor = Cursors.SizeNESW;
                                break;
                            case OperationState.MoveVanishPoint:
                                cpView.Cursor = Cursors.SizeAll;
                                break;
                            default:
                                cpView.Cursor = Cursors.Default;
                                break;
                        }
                    }
                    break;
                case OperationState.ResizeBottom:
                    {
                        int dy = y - oy;
                        oy = y;
                        if (backRect.Height + dy > 0)
                            backRect.Height += dy;
                    }
                    break;
                case OperationState.ResizeTop:
                    {
                        int dy = y - oy;
                        oy = y;
                        if (backRect.Height - dy > 0)
                        {
                            backRect.Y += dy;
                            backRect.Height -= dy;
                        }
                    }
                    break;
                case OperationState.ResizeLeft:
                    {
                        int dx = x - ox;
                        ox = x;
                        if (backRect.Width - dx > 0)
                        {
                            backRect.X += dx;
                            backRect.Width -= dx;
                        }
                    }
                    break;
                case OperationState.ResizeRight:
                    {
                        int dx = x - ox;
                        ox = x;
                        if (backRect.Width + dx > 0)
                            backRect.Width += dx;
                    }
                    break;
                case OperationState.ResizeRightBottom:
                    {
                        int dx = x - ox;
                        ox = x;
                        if (backRect.Width + dx > 0)
                            backRect.Width += dx;
                        int dy = y - oy;
                        oy = y;
                        if (backRect.Height + dy > 0)
                            backRect.Height += dy;
                    }
                    break;
                case OperationState.ResizeTopLeft:
                    {
                        int dy = y - oy;
                        oy = y;
                        if (backRect.Height - dy > 0)
                        {
                            backRect.Y += dy;
                            backRect.Height -= dy;
                        }
                        int dx = x - ox;
                        ox = x;
                        if (backRect.Width - dx > 0)
                        {
                            backRect.X += dx;
                            backRect.Width -= dx;
                        }
                    }
                    break;
                case OperationState.ResizeTopRight:
                    {
                        int dy = y - oy;
                        oy = y;
                        if (backRect.Height - dy > 0)
                        {
                            backRect.Y += dy;
                            backRect.Height -= dy;
                        }
                        int dx = x - ox;
                        ox = x;
                        if (backRect.Width + dx > 0)
                            backRect.Width += dx;
                    }
                    break;
                case OperationState.ResizeLeftBottom:
                    {
                        int dy = y - oy;
                        oy = y;
                        if (backRect.Height + dy > 0)
                            backRect.Height += dy;
                        int dx = x - ox;
                        ox = x;
                        if (backRect.Width - dx > 0)
                        {
                            backRect.X += dx;
                            backRect.Width -= dx;
                        }
                    }
                    break;
                case OperationState.MoveVanishPoint:
                    {
                        int dx = x - ox;
                        int dy = y - oy;
                        oy = y;
                        ox = x;
                        vanishPoint.X += dx;
                        vanishPoint.Y += dy;
                        if (vanishPoint.X < backRect.X + 1)
                            vanishPoint.X = backRect.X + 1;
                        if (vanishPoint.X > backRect.Right - 1)
                            vanishPoint.X = backRect.Right - 1;
                        if (vanishPoint.Y < backRect.Y + 1)
                            vanishPoint.Y = backRect.Y + 1;
                        if (vanishPoint.Y > backRect.Bottom - 1)
                            vanishPoint.Y = backRect.Bottom - 1;
                    }
                    break;
            }
            ClampImageRectangle();
            cpView.RefreshOverlay();
        }

        private void ClampImageRectangle()
        {
            if (backRect.X < 0)
                backRect.X = 0;
            if (backRect.Y < 0)
                backRect.Y = 0;
            var bitmap = doc.GetBitmap();
            if (backRect.Y > vanishPoint.Y - 1)
                backRect.Y = vanishPoint.Y - 1;
            if (backRect.X > vanishPoint.X - 1)
                backRect.X = vanishPoint.X - 1;
            if (backRect.Bottom < vanishPoint.Y + 1)
                backRect.Height = vanishPoint.Y + 1 - backRect.Top;
            if (backRect.Right < vanishPoint.X + 1)
                backRect.Width = vanishPoint.X + 1 - backRect.Left;
            if (backRect.Bottom > bitmap.Height)
            {
                backRect.Y = backRect.Height - backRect.Height;
                if (backRect.Y < 0)
                {
                    backRect.Y = 0;
                    backRect.Height = bitmap.Height - backRect.Y;
                }
            }
            if (backRect.Right > bitmap.Width)
            {
                backRect.X = bitmap.Width - backRect.Width;
                if (backRect.X < 0)
                {
                    backRect.X = 0;
                    backRect.Width = bitmap.Width - backRect.X;
                }
            }
        }
        private void UpdateRect_CreateRect(int x, int y)
        {
            nx = x;
            ny = y;
            backRect.Height = Math.Abs(ny - oy);
            backRect.Width = Math.Abs(nx - ox);
            if (nx < ox)
                Swap(ref ox, ref nx);
            if (ny < oy)
                Swap(ref oy, ref ny);
        }
        private void Swap(ref int ox, ref int nx)
        {
            int tmp = ox;
            ox = nx;
            nx = tmp;
        }

        private void view_ImageMouseUp(int x, int y)
        {
           state = OperationState.CreatedRectangle;
           cpView.RefreshOverlay();
        }

        private Rectangle GetScreenSpaceRectangle(Rectangle rect)
        {
            Point p0 = new Point(rect.Left, rect.Top);
            Point p1 = new Point(rect.Right, rect.Bottom);
            Point scrP0 = cpView.ImageSpacePointToScreenSpacePoint(p0);
            Point scrP1 = cpView.ImageSpacePointToScreenSpacePoint(p1);
            return new Rectangle(scrP0.X, scrP0.Y, scrP1.X - scrP0.X, scrP1.Y - scrP0.Y);
        }

        private Rectangle GetScreenSpaceRectangle()
        {
            return GetScreenSpaceRectangle(backRect);
        }

        enum OperationState
        {
            CreatingRectangle, CreatedRectangle, MoveRectangle, ResizeTop, ResizeLeft, ResizeBottom, ResizeRight,
            ResizeRightBottom, ResizeTopLeft, ResizeLeftBottom, ResizeTopRight, MoveVanishPoint
        }

        struct Float3
        {
            public float x,y,z;
            public Float3(float X, float Y, float Z)
            {
                x = X;
                y = Y;
                z = Z;
            }
            public static Float3 operator *(Float3 v, float u)
            {
                Float3 rs = new Float3(v.x * u, v.y * u, v.z * u);
                return rs;
            }
            public static Float3 operator +(Float3 v, Float3 u)
            {
                Float3 rs = new Float3(v.x + u.x, v.y + u.y, v.z + u.z);
                return rs;
            }
            public static Float3 Cross(Float3 v1, Float3 v2)
            {
                Float3 rs;
                rs.x = v1.y * v2.z - v1.z * v2.y;
                rs.y = v1.z * v2.x - v1.x * v2.z;
                rs.z = v1.x * v2.y - v1.y * v2.x;
                return rs;
            }
        }

        struct ScreenPoint
        {
            public float sx, sy;
        }

        class Camera
        {
            float alpha, beta;
            Float3 pos, up, dir;
            public bool CanFly = true;
            public Camera()
            {
	            alpha = (float)Math.PI;
	            beta = 0.0f;
                pos = new Float3(0.0f, 0.0f, 0.0f);
                up = new Float3(0.0f, 1.0f, 0.0f);
                dir = new Float3(0.0f, 0.0f, -1.0f);
	            CanFly = true;
            }

            public void GoForward(float u)
            {
                Float3 vp = new Float3();
	            if (CanFly) 
	            {
		            pos = pos + dir*u;
	            }
	            else
	            {
		            vp.x = (float)Math.Sin(alpha);
                    vp.z = (float)Math.Cos(alpha);
		            pos = pos + vp*u;
	            }
            }

            public void MoveLeft(float u)
            {
	            Float3 l, vp;
                vp.x = (float)Math.Sin(alpha);
                vp.z = (float)Math.Cos(alpha);
	            l.x=vp.z;	l.y=0;	l.z=-vp.x;
	            pos += l*u;
            }

            public void TurnLeft(float u)
            {
	            alpha += u;
            }

            public void TurnUp(float u)
            {
	            beta += u;
	            if (beta > (float)Math.PI/2)
                    beta = (float)Math.PI / 2;
                if (beta < (float)-Math.PI / 2)
                    beta = -(float)Math.PI / 2;
            }

            public void Transform()
            {
                dir = new Float3((float)Math.Sin(alpha) * (float)Math.Cos(beta),
                           (float)Math.Sin(beta),
                           (float)Math.Cos(alpha) * (float)Math.Cos(beta));
                up = new Float3((float)Math.Sin(alpha) * (float)Math.Cos(Math.PI / 2 + beta),
                          (float)Math.Sin(Math.PI / 2 + beta),
                          (float)Math.Cos(alpha) * (float)Math.Cos(Math.PI / 2 + beta));
                float[] m = new float[16];
                var x = Float3.Cross(dir, up);               
                m[0] = x.x; m[4] = x.y; m[8] = x.z; m[3] = 0.0f;
                m[1] = up.x; m[5] = up.y; m[9] = up.z; m[7] = 0.0f;
                m[2] = -dir.x; m[6] = -dir.y; m[10] = -dir.z; m[11] = 0.0f;
                m[12] = 0.0f; m[13] = 0.0f; m[14] = 0.0f; m[15] = 1.0f;
                GL.LoadMatrix(m);
                GL.Translate(-pos.x, -pos.y, -pos.z);
            }

            public void MoveUp(float u)
            {
                up = new Float3((float)Math.Sin(alpha) * (float)Math.Cos(Math.PI / 2 + beta),
                          (float)Math.Sin(Math.PI / 2 + beta),
                          (float)Math.Cos(alpha) * (float)Math.Cos(Math.PI / 2 + beta));
                pos = pos + up * u;
            }
        }

        class SceneContext : IDisposable
        {
            ScreenPoint[] estimatedVertex = new ScreenPoint[13];
            Float3[] estimatedVertex3d = new Float3[13];
            Bitmap[] surfaceImages = new Bitmap[5];
            uint[] textures = new uint[5];
            public float DepthScale { get; set; }
            public void SetProjection()
            {
                float farRatio = estimatedVertex[7].sy - estimatedVertex[1].sy;
                GL.MatrixMode(MatrixMode.Projection);
                GL.LoadIdentity();
                float xmin = estimatedVertex3d.Min(x => x.x);
                float xmax = estimatedVertex3d.Max(x => x.x);
                float ymin = estimatedVertex3d.Min(x => x.y);
                float ymax = estimatedVertex3d.Max(x => x.y);
                float zmax = estimatedVertex3d.Min(x => x.z);
                float zmin = zmax * farRatio;
                float alpha = 0.001f;
                float sizeScale = 1.0f;
                float zScale = 1.0f;
                if (DepthScale > 1.0)
                    zScale = DepthScale;
                else
                    sizeScale = 1.0f / DepthScale;
                sizeScale = 1.0f;
                zScale = 1.0f;
                GL.Frustum(xmin * alpha * sizeScale, xmax * alpha * sizeScale, ymin * alpha * sizeScale, ymax * alpha * sizeScale, -zmin * alpha * zScale, -zmax / alpha * zScale);
                GL.MatrixMode(MatrixMode.Modelview);
                GL.Enable(EnableCap.DepthTest);
            }

            public void Draw()
            {
                SetProjection();
                GL.Color4(1.0,1.0,1.0,1.0);
                GL.BindTexture(TextureTarget.Texture2D, textures[0]);
                DrawQuad(1, 2, 4, 3);
                GL.BindTexture(TextureTarget.Texture2D, textures[1]);
                DrawQuad(1, 7, 11, 5);
                GL.BindTexture(TextureTarget.Texture2D, textures[2]);
                DrawQuad(7, 8, 10, 9);
                GL.BindTexture(TextureTarget.Texture2D, textures[3]);
                DrawQuad(8, 2, 6, 12);
                GL.BindTexture(TextureTarget.Texture2D, textures[4]);
                DrawQuad(7, 1, 2, 8);
            }

            private void DrawQuad(params int[] indices)
            {
                float sizeScale = 1.0f;
                float zScale = 1.0f;
                if (DepthScale > 1.0)
                    zScale = DepthScale;
                else
                    sizeScale = 1.0f / DepthScale;
                GL.Begin(BeginMode.Quads);
                GL.TexCoord2(0.001, 0.001);
                GL.Vertex3(estimatedVertex3d[indices[0]].x * sizeScale, estimatedVertex3d[indices[0]].y * sizeScale, estimatedVertex3d[indices[0]].z * zScale);
                GL.TexCoord2(0.001, 0.999);
                GL.Vertex3(estimatedVertex3d[indices[1]].x * sizeScale, estimatedVertex3d[indices[1]].y * sizeScale, estimatedVertex3d[indices[1]].z * zScale);
                GL.TexCoord2(0.999, 0.999);
                GL.Vertex3(estimatedVertex3d[indices[2]].x * sizeScale, estimatedVertex3d[indices[2]].y * sizeScale, estimatedVertex3d[indices[2]].z * zScale);
                GL.TexCoord2(0.999, 0.001);
                GL.Vertex3(estimatedVertex3d[indices[3]].x * sizeScale, estimatedVertex3d[indices[3]].y * sizeScale, estimatedVertex3d[indices[3]].z * zScale);
                GL.End();
            }

            private void CreateTextures(Bitmap bitmap)
            {
                GenTexture(0, CreateSurfaceTexture(bitmap, 1, 2, 4, 3));
                GenTexture(1, CreateSurfaceTexture(bitmap, 1, 7, 11, 5));
                GenTexture(2, CreateSurfaceTexture(bitmap, 7, 8, 10, 9));
                GenTexture(3, CreateSurfaceTexture(bitmap, 8, 2, 6, 12));
                GenTexture(4, CreateSurfaceTexture(bitmap, 7, 1, 2, 8));
            }

            private void GenTexture(int id, Bitmap bitmap)
            {
                surfaceImages[id] = bitmap;
                GL.Enable(EnableCap.Texture2D);
                uint texId;
                GL.GenTextures(1, out texId);
                GL.BindTexture(TextureTarget.Texture2D, texId);
                BitmapData data = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height),
                    ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);

                GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, data.Width, data.Height, 0,
                    OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, data.Scan0);

                bitmap.UnlockBits(data);
                GL.TexParameter(TextureTarget.Texture2D, (TextureParameterName)ExtTextureFilterAnisotropic.TextureMaxAnisotropyExt, 16);
                GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.LinearMipmapLinear);
                GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
                GL.GenerateMipmap(GenerateMipmapTarget.Texture2D);
                textures[id] = texId;
            }

            private Bitmap CreateSurfaceTexture(Bitmap bitmap, params int[] indices)
            {
                float[] x = new float[4];
                float[] y = new float[4];
                for (int i = 0; i < 4; i++)
                {
                    x[i] = estimatedVertex[indices[i]].sx;
                    y[i] = estimatedVertex[indices[i]].sy;
                }
                return Homography.Homography.RectifyImage(bitmap, x, y);
            }

            public SceneContext(Bitmap image, Point vanishPoint, Rectangle backRect)
            {
                ScreenPoint[] ctrlPoint = new ScreenPoint[5];
                float w = (float)image.Width;
                float h = (float)image.Height;
                ctrlPoint[0] = new ScreenPoint() { sx = vanishPoint.X / w, sy = (h-vanishPoint.Y)/h };
                ctrlPoint[1] = new ScreenPoint() { sx = backRect.Left / w, sy = (h-backRect.Bottom)/h };
                ctrlPoint[2] = new ScreenPoint() { sx = backRect.Right / w, sy = (h-backRect.Bottom)/h };
                ctrlPoint[3] = new ScreenPoint() { sx = backRect.Right / w, sy = (h-backRect.Top)/h };
                ctrlPoint[4] = new ScreenPoint() { sx = backRect.Left / w, sy = (h - backRect.Top) / h };
                ComputeVertices(ctrlPoint);
                CreateTextures(image);
                DepthScale = 1.0f;
            }

            private void ComputeVertices(ScreenPoint[] ctrlPoint)
            {
                float[] gradient = new float[4];
                for (int i = 0; i < 4; i++)
                    gradient[i] = (ctrlPoint[i + 1].sy - ctrlPoint[0].sy) / (ctrlPoint[i + 1].sx - ctrlPoint[0].sx);
                estimatedVertex[0].sx = ctrlPoint[0].sx;
                estimatedVertex[0].sy = ctrlPoint[0].sy;
                // 1
                estimatedVertex[1].sx = ctrlPoint[1].sx;
                estimatedVertex[1].sy = ctrlPoint[1].sy;
                // 2
                estimatedVertex[2].sx = ctrlPoint[2].sx;
                estimatedVertex[2].sy = ctrlPoint[2].sy;
                // 8
                estimatedVertex[8].sx = ctrlPoint[3].sx;
                estimatedVertex[8].sy = ctrlPoint[3].sy;
                // 7
                estimatedVertex[7].sx = ctrlPoint[4].sx;
                estimatedVertex[7].sy = ctrlPoint[4].sy;
                // (3, 5)
                estimatedVertex[3].sx = (0.0f - ctrlPoint[0].sy) / gradient[0] + ctrlPoint[0].sx;
                estimatedVertex[3].sy = 0.0f; estimatedVertex[5].sx = 0.0f;
                estimatedVertex[5].sy = (0.0f - ctrlPoint[0].sx) * gradient[0] + ctrlPoint[0].sy;
                // (4, 6)
                estimatedVertex[4].sx = (0.0f - ctrlPoint[0].sy) / gradient[1] + ctrlPoint[0].sx;
                estimatedVertex[4].sy = 0.0f; estimatedVertex[6].sx = 1.0f;
                estimatedVertex[6].sy = (1.0f - ctrlPoint[0].sx) * gradient[1] + ctrlPoint[0].sy;
                // (10, 12)
                estimatedVertex[10].sx = (1.0f - ctrlPoint[0].sy) / gradient[2] + ctrlPoint[0].sx;
                estimatedVertex[10].sy = 1.0f; estimatedVertex[12].sx = 1.0f;
                estimatedVertex[12].sy = (1.0f - ctrlPoint[0].sx) * gradient[2] + ctrlPoint[0].sy;
                // (9, 11)
                estimatedVertex[9].sx = (1.0f - ctrlPoint[0].sy) / gradient[3] + ctrlPoint[0].sx;
                estimatedVertex[9].sy = 1.0f; estimatedVertex[11].sx = 0.0f;
                estimatedVertex[11].sy = (0.0f - ctrlPoint[0].sx) * gradient[3] + ctrlPoint[0].sy;

                // compute 3d vertices
                float grad;
                float height;
                Float3 eye;
                eye.x = ctrlPoint[0].sx; eye.y = ctrlPoint[0].sy; eye.z = 0.0f;
                for (int i = 1; i <= 6; i++)
                {
                    grad = -eye.y / (estimatedVertex[i].sy - eye.y);
                    estimatedVertex3d[i].x = grad * (estimatedVertex[i].sx - eye.x) + eye.x;
                    estimatedVertex3d[i].z = grad * (-1.0f - eye.z) + eye.z;
                    estimatedVertex3d[i].y = 0.0f;
                }
                estimatedVertex3d[0].x = ctrlPoint[0].sx;
                estimatedVertex3d[0].y = ctrlPoint[0].sy;
                estimatedVertex3d[0].z = estimatedVertex3d[1].z;

                height = (estimatedVertex[7].sy - estimatedVertex[1].sy) / (-1.0f) * estimatedVertex3d[1].z;
                estimatedVertex3d[7].x = estimatedVertex3d[1].x; estimatedVertex3d[8].x = estimatedVertex3d[2].x;
                estimatedVertex3d[7].y = estimatedVertex3d[8].y = height;
                estimatedVertex3d[7].z = estimatedVertex3d[1].z; estimatedVertex3d[8].z = estimatedVertex3d[2].z;

                for (int i = 9; i <= 12; i++)
                {
                    grad = (height - eye.y) / (estimatedVertex[i].sy - eye.y);
                    estimatedVertex3d[i].x = grad * (estimatedVertex[i].sx - eye.x) + eye.x;
                    estimatedVertex3d[i].z = grad * (-1.0f - eye.z) + eye.z;
                    estimatedVertex3d[i].y = height;
                }

                for (int i = 1; i < 13; i++)
                {
                    estimatedVertex3d[i].x -= eye.x;
                    estimatedVertex3d[i].y -= eye.y;
                }
            }

            public void WriteObj(string fileName)
            {
                using (StreamWriter writer = new StreamWriter(fileName))
                {
                    float sizeScale = 1.0f;
                    float zScale = 1.0f;
                    if (DepthScale > 1.0)
                        zScale = DepthScale;
                    else
                        sizeScale = 1.0f / DepthScale;
                    for (int i = 1; i < estimatedVertex3d.Length; i++)
                        writer.WriteLine(string.Format("v {0} {1} {2}", estimatedVertex3d[i].x * sizeScale, estimatedVertex3d[i].y * sizeScale, estimatedVertex3d[i].z * zScale));
                    writer.WriteLine(string.Format("vt {0} {1}", 0.0, 0.0));
                    writer.WriteLine(string.Format("vt {0} {1}", 0.0, 1.0));
                    writer.WriteLine(string.Format("vt {0} {1}", 1.0, 1.0));
                    writer.WriteLine(string.Format("vt {0} {1}", 1.0, 0.0));
                    writer.WriteLine(string.Format("f {0}/{4} {1}/{5} {2}/{6} {3}/{7}", 1, 3, 4, 2, 1, 2, 3, 4));
                    writer.WriteLine(string.Format("f {0}/{4} {1}/{5} {2}/{6} {3}/{7}", 1, 7, 11, 5, 1, 2, 3, 4));
                    writer.WriteLine(string.Format("f {0}/{4} {1}/{5} {2}/{6} {3}/{7}", 7, 8, 10, 9, 1, 2, 3, 4));
                    writer.WriteLine(string.Format("f {0}/{4} {1}/{5} {2}/{6} {3}/{7}", 2, 6, 12, 8, 1, 2, 3, 4));
                    writer.WriteLine(string.Format("f {0}/{4} {1}/{5} {2}/{6} {3}/{7}", 1, 2, 8, 7, 1, 2, 3, 4));
                }
                for (int i = 0; i < 5; i++)
                    surfaceImages[i].Save(fileName + "." + i.ToString() + ".bmp");
            }

            public void Dispose()
            {
                GL.DeleteTextures(5, textures);
                foreach (var bmp in surfaceImages)
                    bmp.Dispose();
            }
        }

        SceneContext scene = null;

        private void ImageWalkForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (scene != null)
                scene.Dispose();
        }

        private void glControl_Load(object sender, EventArgs e)
        {
            var bitmap = doc.GetBitmap();
            
            ResetViewPort(bitmap.Width, bitmap.Height);
            glControl.Resize += glControl_Resize;
        }

        int vox, voy;
        Camera cam = new Camera();
        private void glControl_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.Button == System.Windows.Forms.MouseButtons.Left)
            {
                float dx = e.X - vox;
                float dy = e.Y - voy;
                if (Control.ModifierKeys == Keys.Shift)
                {
                    cam.GoForward(dy / glControl.Height);
                }
                else if (Control.ModifierKeys == Keys.Alt)
                {
                    cam.TurnLeft(dx / 256 * (float)Math.PI);
                    cam.TurnUp(-dy / 256 * (float)Math.PI);
                }
                else if (Control.ModifierKeys == Keys.None)
                {
                    cam.MoveLeft(dx / glControl.Width);
                    cam.MoveUp(dy / glControl.Height);
                }
                vox = e.X;
                voy = e.Y;
            }
            glControl.Refresh();
            
        }

        private void glControl_MouseDown(object sender, MouseEventArgs e)
        {
            vox = e.X;
            voy = e.Y;
        }

        private void btnExport_Click(object sender, EventArgs e)
        {
            if (scene != null)
            {
                using (var dlg = new SaveFileDialog())
                {
                    dlg.DefaultExt = "obj";
                    dlg.AddExtension = true;
                    dlg.Filter = "Wavefront Object|*.obj|All Files|*.*";
                    if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                    {
                        scene.WriteObj(dlg.FileName);
                    }
                }
            }
        }

        private void txtSceneDepth_TextChanged(object sender, EventArgs e)
        {
            float scale = 1.0f;
            if (float.TryParse(txtSceneDepth.Text, out scale))
            {
                if (scale >= 0.001f && scene != null)
                {
                    scene.DepthScale = scale;
                    glControl.Refresh();
                }
            }
        }
    }
}
